package org.person.gift.service.impl;

import cn.hutool.core.lang.generator.SnowflakeGenerator;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import org.person.gift.clients.GiftClient;
import org.person.gift.domain.GoodsSku;
import org.person.gift.domain.ShopCar;
import org.person.gift.dto.AddShopCarDTO;
import org.person.gift.dto.DeleteDTO;
import org.person.gift.dto.UpdateNumDTO;
import org.person.gift.dto.UpdateSelectDTO;
import org.person.gift.exception.BusinessException;
import org.person.gift.exception.ResponseCode;
import org.person.gift.exception.SystemAssert;
import org.person.gift.mapper.ShopCarMapper;
import org.person.gift.service.IShopCarService;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import org.person.gift.strategy.IShopCarDataSyncKeyGroupStrategy;
import org.person.gift.strategy.ShopCarDataSyncKeyGroupStrategyFactory;
import org.person.gift.util.AjaxResult;
import org.person.gift.util.encrypt.TenantIdName;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author stark
 * @since 2024-02-21
 */
@Service
public class ShopCarServiceImpl extends ServiceImpl<ShopCarMapper, ShopCar> implements IShopCarService {

    @Autowired
    private GiftClient giftClient;

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @Value("${person.shopCar.keyGroupStrategy}")
    private String keyGroupStrategy;

    private static final String PREFIX = "shop_car_prefix_";

    @Override
    @Transactional
    public void addShopCar(AddShopCarDTO dto) {
        String key = PREFIX+dto.getUserId();
        // 1 根据当前userId去redis中查询购物车数据
        BoundHashOperations hashOps = redisTemplate.boundHashOps(key);
        List<String> values = hashOps.values();

        // 2 如果没有,去数据库中查询该用户的购物车数据
        if(values == null || values.size() == 0){
            Wrapper<ShopCar> wrapper = new EntityWrapper<ShopCar>().eq("user_id", dto.getUserId());
            List<ShopCar> shopCars = selectList(wrapper);
            // 2.1 如果数据库中没有数据,直接将当前数据添加到redis
            if(shopCars == null || shopCars.size() == 0){
                ShopCar car = getShopCar(dto);
                // 把它存入redis
                hashOps.put(dto.getSkuId()+"", JSONObject.toJSONString(car));
            }else{
                // 2.2 如果数据库中有数据
                // 2.2.1 就将数据库中的数据添加到redis
                Boolean flag = false;
                for (ShopCar shopCar : shopCars) {
                    // 把数据库中查询出来的购物车数据,都添加到redis
                    // 2.2.2 如果数据库中包含当前要新增的商品,就修改num后放入redis
                    if(shopCar.getSkuId().equals(dto.getSkuId())){
                        flag = true;
                        shopCar.setNum(shopCar.getNum() + dto.getNum());
                    }
                    hashOps.put(shopCar.getSkuId()+"",JSONObject.toJSONString(shopCar));
                }
                // 2.2.3 如果数据库中不包含当前新增的商品,就直接新增数据到redis
                // 方案一: 根据dto的skuId进行过筛选,没有数据就说明不存在,就要重新构建并放入redis
                // 方案二: 判断flag
                if(!flag){
                    ShopCar shopCar = getShopCar(dto);
                    hashOps.put(shopCar.getSkuId()+"",JSONObject.toJSONString(shopCar));
                }
            }
        }else{
            // 3 如果redis中有数据
            String str = (String)hashOps.get(dto.getSkuId().toString());

            // 3.1 如果当前新增的商品在里面
            if(StrUtil.isNotBlank(str)){
                ShopCar shopCar = JSONObject.parseObject(str, ShopCar.class);
                if(shopCar.getDelete()){
                    // 3.1.2 这个商品的isDelete = 1,怎么办??
                    // 查询当前商品信息,重新构建shopCar,把isDelete=0,覆盖之前的value
                    shopCar = getShopCar(dto);
                }else{
                    // 3.1.1 这个商品的isDelete = 0,修改num
                    shopCar.setNum(shopCar.getNum() + dto.getNum());
                }
                hashOps.put(shopCar.getSkuId()+"",JSONObject.toJSONString(shopCar));
            }else{
                // 3.2 如果当前新增的商品不在里面,就新增数据到redis
                ShopCar shopCar = getShopCar(dto);
                hashOps.put(shopCar.getSkuId()+"",JSONObject.toJSONString(shopCar));
            }
        }

        // 给redis数据设置过期时间,尽量不要用24h,要给同步数据留一点余地
        hashOps.expire(26, TimeUnit.HOURS);
    }

    private ShopCar getShopCar(AddShopCarDTO dto) {
        // 4.1 调用礼物服务查询skuId的相关商品信息
        AjaxResult ajaxResult = giftClient.get(dto.getSkuId());
        boolean success = ajaxResult.isSuccess();
        if(!success){
            // 如果远程调用失败,就返回错误信息
            throw new BusinessException(ajaxResult.getMessage());
        }

        // 4.2 封装shopCar对象
        String jsonStr = JSONObject.toJSONString(ajaxResult.getResultObj());
        GoodsSku goodsSku = JSONObject.parseObject(jsonStr,GoodsSku.class);
        ShopCar car = new ShopCar();
        car.setId(new SnowflakeGenerator().next());
        car.setSpuId(goodsSku.getSpuId());
        car.setSpuName(goodsSku.getSpuName());
        car.setSkuId(goodsSku.getId());
        car.setSkuInfo(goodsSku.getSkuName());
        car.setAddPrice(goodsSku.getPrice());
        car.setPrice(goodsSku.getPrice());
        car.setUserId(dto.getUserId());
        car.setUsername(dto.getUsername());
        car.setTenantId(goodsSku.getTenantId());
        car.setTenantName(goodsSku.getTenantId()+"");
        car.setSelect(true);    // 第一次新增的时候,默认是选中
        car.setCreateTime(new Date());
        car.setNum(dto.getNum());
        return car;
    }

    @Override
    public void updateNum(UpdateNumDTO dto) {
        // 1 根据userId查询redis中的购物车数据
        String key = PREFIX+dto.getUserId();
        // 1 根据当前userId去redis中查询购物车数据
        BoundHashOperations hashOps = redisTemplate.boundHashOps(key);

        // 2 如果购物车数据为空,说明参数有问题,返回错误信息
        List values = hashOps.values();
        SystemAssert.isNotNull(values, ResponseCode.RESPONSE_CODE_500);

        // 3 如果不为空,就修改数量,并重新放入redis
        String str = (String)hashOps.get(dto.getSkuId().toString());
        ShopCar shopCar = JSONObject.parseObject(str, ShopCar.class);
        shopCar.setNum(dto.getNum());
        hashOps.put(dto.getSkuId()+"",JSONObject.toJSONString(shopCar));

        // 4 更新过期时间
        hashOps.expire(26,TimeUnit.HOURS);
    }

    @Override
    public void updateSelect(UpdateSelectDTO dto) {
        // 1 根据userId查询redis中的购物车数据
        String key = PREFIX+dto.getUserId();
        // 1 根据当前userId去redis中查询购物车数据
        BoundHashOperations hashOps = redisTemplate.boundHashOps(key);

        // 2 如果购物车数据为空,说明参数有问题,返回错误信息
        List values = hashOps.values();
        SystemAssert.isNotNull(values, ResponseCode.RESPONSE_CODE_500);

        // 3 如果不为空,就修改状态,并重新放入redis
        String str = (String)hashOps.get(dto.getSkuId().toString());
        ShopCar shopCar = JSONObject.parseObject(str, ShopCar.class);
        shopCar.setSelect(dto.getSelect());
        hashOps.put(dto.getSkuId()+"",JSONObject.toJSONString(shopCar));

        // 4 更新过期时间
        hashOps.expire(26,TimeUnit.HOURS);
    }

    @Override
    public Map<TenantIdName, List<ShopCar>> getShopCarList(Long userId) {
        // 1 根据userId去redis中查询
        String key = PREFIX+userId;
        BoundHashOperations hashOps = redisTemplate.boundHashOps(key);
        List<String> values = hashOps.values();

        // 2 如果redis有
        if(values != null && values.size() > 0){
            List<ShopCar> shopCars = values.stream().map(x -> JSONObject.parseObject(x, ShopCar.class)).collect(Collectors.toList());
            // 2.1 筛选出isDelete=false的数据
            shopCars = shopCars.stream().filter(x -> x.getDelete() == false).collect(Collectors.toList());

            // 2.2 根据店铺ID分组后再返回
            Map<TenantIdName, List<ShopCar>> listMap = shopCars.stream().collect(Collectors.groupingBy(x ->
                    new TenantIdName(x.getTenantId(), x.getTenantName())));
            return listMap;
        }
        // 3 如果redis没有数据
        // 3.1 从数据库中查询该用户的购物车数据
        Wrapper<ShopCar> wrapper = new EntityWrapper<ShopCar>().eq("user_id", userId);
        List<ShopCar> shopCars = selectList(wrapper);

        // 3.2 将数据存入redis
        for (ShopCar shopCar : shopCars) {
            hashOps.put(shopCar.getSkuId()+"",JSONObject.toJSONString(shopCar));
        }

        // 更新redis的过期时间
        hashOps.expire(26,TimeUnit.HOURS);

        // 3.3 分组后再返回
        Map<TenantIdName, List<ShopCar>> listMap = shopCars.stream().collect(
                Collectors.groupingBy(x -> new TenantIdName(x.getTenantId(), x.getTenantName())));
        return listMap;
    }

    @Override
    public void deleteByRedis(DeleteDTO dto) {
        // 1 根据userId查询redis中的购物车数据
        String key = PREFIX+dto.getUserId();
        // 1 根据当前userId去redis中查询购物车数据
        BoundHashOperations hashOps = redisTemplate.boundHashOps(key);

        // 2 如果购物车数据为空,说明参数有问题,返回错误信息
        List values = hashOps.values();
        SystemAssert.isNotNull(values, ResponseCode.RESPONSE_CODE_500);
        // 3 如果不为空,就要修改isDelete=true,要把删除状态标识为已删除
        String str = (String)hashOps.get(dto.getSkuId().toString());
        ShopCar shopCar = JSONObject.parseObject(str, ShopCar.class);
        shopCar.setDelete(true);
        hashOps.put(dto.getSkuId()+"",JSONObject.toJSONString(shopCar));

        // 4 更新过期时间
        hashOps.expire(26,TimeUnit.HOURS);
    }

    @Override
    public void dataSync() {
        // 1 获取redis中所有用户的购物车的key
        Set<String> keys = redisTemplate.keys(PREFIX + "*");
        System.out.println(keys);
        IShopCarDataSyncKeyGroupStrategy groupStrategy = ShopCarDataSyncKeyGroupStrategyFactory
                .getStrategy(keyGroupStrategy);
        // key是分组后的组名  value:购物车用户的key
        Map<String, List<String>> listMap = groupStrategy.group(keys);
        System.out.println(listMap);

        // 3 分组以后,再对每个分组里面的数据做批量同步
        Set<Map.Entry<String, List<String>>> entrySet = listMap.entrySet();
        for (Map.Entry<String, List<String>> entry : entrySet) {
            // 第1次循环:List<String>就是第一组里面的所有用户的购物车key   shop_car_prefix_16
            List<String> groupKeys = entry.getValue();

            // 要被删除的数据的ID
            List<Long> delIds = new ArrayList<>();
            List<ShopCar> addUpdateList = new ArrayList<>();

            // 遍历这个组里面所有的key,根据key获取这个用户在redis中的购物车数据
            for (String key : groupKeys) {
                BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(key);
                // 这是该用户的所有购物车数据
                List<String> list = hashOps.values();
                // shopCars里面包含 新增,修改,删除
                List<ShopCar> shopCars = list.stream().map(x -> JSONObject.parseObject(x, ShopCar.class)).collect(Collectors.toList());

                // 过滤出已经删除的数据
                List<ShopCar> delList = shopCars.stream().filter(x -> x.getDelete()).collect(Collectors.toList());
                delIds.addAll(delList.stream().map(ShopCar::getId).collect(Collectors.toList()));

                // 过滤出当前没有被删除的数据
                shopCars = shopCars.stream().filter(x -> x.getDelete() == false).collect(Collectors.toList());
                addUpdateList.addAll(shopCars);
            }
            // 删除数据库中的数据
            if (delIds != null && delIds.size() > 0) {
                deleteBatchIds(delIds);
            }

            // 新增/修改数据库数据
            if (addUpdateList != null && addUpdateList.size() > 0) {
                insertOrUpdateBatch(addUpdateList);
            }
            System.out.println(entry.getKey() + "->这个组的数据已经处理完毕");
        }
    }
}
